package org.cainiao.io.nio.channel.file.lock;

import java.io.File;
import java.io.RandomAccessFile;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.Random;

public class LockTest {

    private static final int SIZEOF_INT = 4;
    private static final int INDEX_START = 0;
    private static final int INDEX_COUNT = 10;
    private static final int INDEX_SIZE = INDEX_COUNT * SIZEOF_INT;
    private final ByteBuffer buffer = ByteBuffer.allocate(INDEX_SIZE);
    private final IntBuffer indexBuffer = buffer.asIntBuffer();
    private final Random rand = new Random();

    public static void main(String[] argv) throws Exception {
        boolean writer;
        if (argv.length != 2) {
            System.out.println("Usage: [ -r | -w ] filename");
            return;
        }
        writer = argv[0].equals("-w");
        URL url = LockTest.class.getClassLoader().getResource(argv[1]);
        if (url == null) {
            throw new RuntimeException("未找到文件");
        }
        try (RandomAccessFile randomAccessFile = new RandomAccessFile(new File(url.getFile()), writer ? "rw" : "r")) {
            FileChannel fc = randomAccessFile.getChannel();
            LockTest lockTest = new LockTest();
            if (writer) {
                lockTest.doUpdates(fc);
            } else {
                lockTest.doQueries(fc);
            }
        }
    }

    // ----------------------------------------------------------------
    // Simulate a series of read-only queries while holding a shared lock on the index area
    void doQueries(FileChannel fc) throws Exception {
        while (true) {
            println("trying for shared lock...");
            FileLock lock = fc.lock(INDEX_START, INDEX_SIZE, true);
            int reps = rand.nextInt(60) + 20;
            for (int i = 0; i < reps; i++) {
                int n = rand.nextInt(INDEX_COUNT);
                int position = INDEX_START + (n * SIZEOF_INT);
                buffer.clear();
                fc.read(buffer, position);
                println("Index entry " + n + "=" + indexBuffer.get(n));
                // Pretend to be doing some work
                Thread.sleep(100);
            }
            lock.release();
            println("<sleeping>");
            Thread.sleep(rand.nextInt(3000) + 500);
        }
    }

    // 模拟持有互斥锁对索引区域进行一系列的更新
    void doUpdates(FileChannel fc) throws Exception {
        while (true) {
            println("trying for exclusive lock...");
            FileLock lock = fc.lock(INDEX_START, INDEX_SIZE, false);
            updateIndex(fc);
            lock.release();
            println("<sleeping>");
            Thread.sleep(rand.nextInt(2000) + 500);
        }
    }

    // Write new values to the index slots
    private int idxval = 1;

    private void updateIndex(FileChannel fc) throws Exception {
        // "indexBuffer" is an int view of "buffer"
        indexBuffer.clear();

        for (int i = 0; i < INDEX_COUNT; i++) {
            idxval++;
            println("Updating index " + i + "=" + idxval);
            indexBuffer.put(idxval);
            // Pretend that this is really hard work
            Thread.sleep(500);
        }
        // leaves position and limit correct for whole buffer
        // TODO 原来的代码是：buffer.clear();，应该是写错了，待验证
        buffer.flip();
        fc.write(buffer, INDEX_START);
    }

    // ----------------------------------------------------------------
    private int lastLineLen = 0;

    /**
     * 在同一行打印消息，每次打印会覆盖前一次打印的消息
     *
     * @param msg 消息
     */
    private void println(String msg) {
        // 回车不换行，从同一行的起始位置开始打印
        System.out.print("\r ");
        System.out.print(msg);
        // 如果上次打印的消息比本次打印的消息更长，则将上次打印的消息多出的部分清空
        for (int i = msg.length(); i < lastLineLen; i++) {
            System.out.print(" ");
        }
        System.out.print("\r");
        System.out.flush();
        lastLineLen = msg.length();
    }

}
